/*
*    Copyright (c) 2013 eryar All Rights Reserved.
*
*        File    : Main.cpp
*        Author  : eryar@163.com
*        Date    : 2013-08-11 10:36
*        Version : V1.0
*
*    Description : Draw OpenCascade Geometry Surfaces in OpenSceneGraph.
 *
 */

#define _MATH_DEFINES_DEFINED

// OpenSceneGraph
#include <QtCore/QTimer>  
#include <QtGui>
#include <QtWidgets/QApplication> 
#include <QtWidgets/QDialog>
#include <QtWidgets/QVBoxLayout>  

#include <osgViewer/Viewer>  
#include <osgViewer/ViewerEventHandlers>
#include <osgDB/ReadFile>  
#include <osgQt/GraphicsWindowQt>  
#include <osgGA/TrackballManipulator>
#include <osgGA/StateSetManipulator>
#include <osg/MatrixTransform> 
#include <osg/NodeVisitor>  
#include <osg/BlendFunc>  

#include <QtOSGWidget.h>
#include <Qt5BaseExt.h>

#include <osgDB/ReadFile>
#include <osgViewer/Viewer>
#include <osgGA/StateSetManipulator>
#include <osgViewer/ViewerEventHandlers>

// OpenCascade
#define WNT
#include <TColgp_Array2OfPnt.hxx>
#include <TColStd_HArray1OfInteger.hxx>
#include <TColGeom_Array2OfBezierSurface.hxx>
#include <GeomConvert_CompBezierSurfacesToBSplineSurface.hxx>

#include <Geom_Surface.hxx>
#include <Geom_BezierSurface.hxx>
#include <Geom_BSplineSurface.hxx>
#include <Geom_ConicalSurface.hxx>
#include <Geom_CylindricalSurface.hxx>
#include <Geom_Plane.hxx>
#include <Geom_ToroidalSurface.hxx>
#include <Geom_SphericalSurface.hxx>

#pragma comment(lib, "TKernel.lib")
#pragma comment(lib, "TKMath.lib")
#pragma comment(lib, "TKG3d.lib")
#pragma comment(lib, "TKGeomBase.lib")

// Approximation Delta.
const double APPROXIMATION_DELTA = 0.1;

/**
* @breif Build geometry surface.
*/
osg::ref_ptr<osg::Node> buildSurface(const Geom_Surface& surface)
{
    osg::ref_ptr<osg::Geode> geode = new osg::Geode();

    gp_Pnt point;
    Standard_Real uFirst = 0.0;
    Standard_Real vFirst = 0.0;
    Standard_Real uLast = 0.0;
    Standard_Real vLast = 0.0;

    surface.Bounds(uFirst, uLast, vFirst, vLast);

    Precision::IsNegativeInfinite(uFirst) ? uFirst = -1.0 : uFirst;
    Precision::IsInfinite(uLast) ? uLast = 1.0 : uLast;

    Precision::IsNegativeInfinite(vFirst) ? vFirst = -1.0 : vFirst;
    Precision::IsInfinite(vLast) ? vLast = 1.0 : vLast;

    // Approximation in v direction.
    for (Standard_Real u = uFirst; u <= uLast; u += APPROXIMATION_DELTA)
    {
        osg::ref_ptr<osg::Geometry> linesGeom = new osg::Geometry();
        osg::ref_ptr<osg::Vec3Array> pointsVec = new osg::Vec3Array();

        for (Standard_Real v = vFirst; v <= vLast; v += APPROXIMATION_DELTA)
        {
            point = surface.Value(u, v);

            pointsVec->push_back(osg::Vec3(point.X(), point.Y(), point.Z()));
        }

        // Set the colors.
        osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array;
        colors->push_back(osg::Vec4(1.0f, 1.0f, 0.0f, 0.0f));
        linesGeom->setColorArray(colors.get());
        linesGeom->setColorBinding(osg::Geometry::BIND_OVERALL);

        // Set the normal in the same way of color.
        osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array;
        normals->push_back(osg::Vec3(0.0f, -1.0f, 0.0f));
        linesGeom->setNormalArray(normals.get());
        linesGeom->setNormalBinding(osg::Geometry::BIND_OVERALL);

        // Set vertex array.
        linesGeom->setVertexArray(pointsVec);
        linesGeom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_STRIP, 0, pointsVec->size()));

        geode->addDrawable(linesGeom.get());
    }

     // Approximation in u direction.
     for (Standard_Real v = vFirst; v <= vLast; v += APPROXIMATION_DELTA)
     {
         osg::ref_ptr<osg::Geometry> linesGeom = new osg::Geometry();
         osg::ref_ptr<osg::Vec3Array> pointsVec = new osg::Vec3Array();

         for (Standard_Real u = vFirst; u <= uLast; u += APPROXIMATION_DELTA)
         {
             point = surface.Value(u, v);

             pointsVec->push_back(osg::Vec3(point.X(), point.Y(), point.Z()));
         }

         // Set the colors.
         osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array;
         colors->push_back(osg::Vec4(1.0f, 1.0f, 0.0f, 0.0f));
         linesGeom->setColorArray(colors.get());
         linesGeom->setColorBinding(osg::Geometry::BIND_OVERALL);

         // Set the normal in the same way of color.
         osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array;
         normals->push_back(osg::Vec3(0.0f, -1.0f, 0.0f));
         linesGeom->setNormalArray(normals.get());
         linesGeom->setNormalBinding(osg::Geometry::BIND_OVERALL);

         // Set vertex array.
         linesGeom->setVertexArray(pointsVec);
         linesGeom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::LINE_STRIP, 0, pointsVec->size()));

         geode->addDrawable(linesGeom.get());
     }

     return geode.release();
 }

 /**
 * @breif Test geometry surfaces of OpenCascade.
 */
 osg::ref_ptr<osg::Group> buildScene(void)
 {
     osg::ref_ptr<osg::Group> root = new osg::Group();

     // Test Plane.
     Geom_Plane plane(gp::XOY());
     root->addChild(buildSurface(plane));

     // Test Bezier Surface and B-Spline Surface.
     TColgp_Array2OfPnt array1(1, 3, 1, 3);
     TColgp_Array2OfPnt array2(1, 3, 1, 3);
     TColgp_Array2OfPnt array3(1, 3, 1, 3);
     TColgp_Array2OfPnt array4(1, 3, 1, 3);

     array1.SetValue(1, 1, gp_Pnt(1, 1, 1));
     array1.SetValue(1, 2, gp_Pnt(2, 1, 2));
     array1.SetValue(1, 3, gp_Pnt(3, 1, 1));
     array1.SetValue(2, 1, gp_Pnt(1, 2, 1));
     array1.SetValue(2, 2, gp_Pnt(2, 2, 2));
     array1.SetValue(2, 3, gp_Pnt(3, 2, 0));
     array1.SetValue(3, 1, gp_Pnt(1, 3, 2));
     array1.SetValue(3, 2, gp_Pnt(2, 3, 1));
     array1.SetValue(3, 3, gp_Pnt(3, 3, 0));

     array2.SetValue(1, 1, gp_Pnt(3, 1, 1));
     array2.SetValue(1, 2, gp_Pnt(4, 1, 1));
     array2.SetValue(1, 3, gp_Pnt(5, 1, 2));
     array2.SetValue(2, 1, gp_Pnt(3, 2, 0));
     array2.SetValue(2, 2, gp_Pnt(4, 2, 1));
     array2.SetValue(2, 3, gp_Pnt(5, 2, 2));
     array2.SetValue(3, 1, gp_Pnt(3, 3, 0));
     array2.SetValue(3, 2, gp_Pnt(4, 3, 0));
     array2.SetValue(3, 3, gp_Pnt(5, 3, 1));

     array3.SetValue(1, 1, gp_Pnt(1, 3, 2));
     array3.SetValue(1, 2, gp_Pnt(2, 3, 1));
     array3.SetValue(1, 3, gp_Pnt(3, 3, 0));
     array3.SetValue(2, 1, gp_Pnt(1, 4, 1));
     array3.SetValue(2, 2, gp_Pnt(2, 4, 0));
     array3.SetValue(2, 3, gp_Pnt(3, 4, 1));
     array3.SetValue(3, 1, gp_Pnt(1, 5, 1));
     array3.SetValue(3, 2, gp_Pnt(2, 5, 1));
     array3.SetValue(3, 3, gp_Pnt(3, 5, 2));

     array4.SetValue(1, 1, gp_Pnt(3, 3, 0));
     array4.SetValue(1, 2, gp_Pnt(4, 3, 0));
     array4.SetValue(1, 3, gp_Pnt(5, 3, 1));
     array4.SetValue(2, 1, gp_Pnt(3, 4, 1));
     array4.SetValue(2, 2, gp_Pnt(4, 4, 1));
     array4.SetValue(2, 3, gp_Pnt(5, 4, 1));
     array4.SetValue(3, 1, gp_Pnt(3, 5, 2));
     array4.SetValue(3, 2, gp_Pnt(4, 5, 2));
     array4.SetValue(3, 3, gp_Pnt(5, 5, 1));

     Geom_BezierSurface BZ1(array1);
     Geom_BezierSurface BZ2(array2);
     Geom_BezierSurface BZ3(array3);
     Geom_BezierSurface BZ4(array4);
     root->addChild(buildSurface(BZ1));
     root->addChild(buildSurface(BZ2));
     root->addChild(buildSurface(BZ3));
     root->addChild(buildSurface(BZ4));

     Handle_Geom_BezierSurface BS1 = new Geom_BezierSurface(array1);
     Handle_Geom_BezierSurface BS2 = new Geom_BezierSurface(array2);
     Handle_Geom_BezierSurface BS3 = new Geom_BezierSurface(array3);
     Handle_Geom_BezierSurface BS4 = new Geom_BezierSurface(array4);
     TColGeom_Array2OfBezierSurface bezierarray(1, 2, 1, 2);
     bezierarray.SetValue(1, 1, BS1);
     bezierarray.SetValue(1, 2, BS2);
     bezierarray.SetValue(2, 1, BS3);
     bezierarray.SetValue(2, 2, BS4);

     GeomConvert_CompBezierSurfacesToBSplineSurface BB(bezierarray);

     if (BB.IsDone())
     {
         Geom_BSplineSurface BSPLSURF(
             BB.Poles()->Array2(),
             BB.UKnots()->Array1(),
             BB.VKnots()->Array1(),
             BB.UMultiplicities()->Array1(),
             BB.VMultiplicities()->Array1(),
             BB.UDegree(),
             BB.VDegree());

         BSPLSURF.Translate(gp_Vec(0, 0, 2));

         root->addChild(buildSurface(BSPLSURF));
     }

     // Test Spherical Surface.
     Geom_SphericalSurface sphericalSurface(gp::XOY(), 1.0);
     sphericalSurface.Translate(gp_Vec(2.5, 0.0, 0.0));
     root->addChild(buildSurface(sphericalSurface));

     // Test Conical Surface.
     Geom_ConicalSurface conicalSurface(gp::XOY(), M_PI / 8, 1.0);
     conicalSurface.Translate(gp_Vec(5.0, 0.0, 0.0));
     root->addChild(buildSurface(conicalSurface));

     // Test Cylindrical Surface.
     Geom_CylindricalSurface cylindricalSurface(gp::XOY(), 1.0);
     cylindricalSurface.Translate(gp_Vec(8.0, 0.0, 0.0));
     root->addChild(buildSurface(cylindricalSurface));

     // Test Toroidal Surface.
     Geom_ToroidalSurface toroidalSurface(gp::XOY(), 1.0, 0.2);
     toroidalSurface.Translate(gp_Vec(11.0, 0.0, 0.0));
     root->addChild(buildSurface(toroidalSurface));

	 return root;
 }

 int main(int argc, char* argv[])
 {

	 QApplication app(argc, argv);
	 QtOSGWidget* viewWidget = new QtOSGWidget(NULL);
	 osg::ref_ptr<osg::Group> root = buildScene();
	 viewWidget->addEventHandler(new osgGA::StateSetManipulator);
	 viewWidget->addEventHandler(new osgViewer::StatsHandler);
	 viewWidget->setCameraManipulator(new osgGA::TrackballManipulator);
	 viewWidget->setSceneData(root);
	 //viewWidget->getCamera()->setClearColor(osg::Vec4(0.176, 0.18, 0.157, 0.9));
	 viewWidget->show();
	 return app.exec();
 }